"use client"; /** * /w/:workspace/settings — workspace settings (index) + danger zone. * * - General: rename the workspace (PATCH /workspaces/:id {name, emoji}) via * the core useUpdateWorkspace. * - Danger zone (admin/owner): * Transfer ownership → POST /workspaces/:id/transfer { newOwnerId } (owner-only * server-side; must be a current member). * Delete workspace → DELETE /workspaces/:id (owner-only server-side; * self-hosted hard delete) with a type-name confirm in the UI. * * Member management lives under /settings/members (the core sidebar deep-links * there); this page links to it. */ import { useState } from "react"; import { useParams } from "next/navigation"; import { useQueryClient } from "@tanstack/react-query"; import { ArrowRightLeft, Loader2, Trash2 } from "lucide-react"; import { toast } from "@/stores/ui-store"; import { apiFetch } from "@/lib/api"; import { useWorkspace, useWorkspaceMembers, useUpdateWorkspace } from "@/hooks/use-workspace"; import { WorkspaceMember } from "@/lib/types"; import { SettingsShell, Section, Field, inputCls } from "@/components/settings/settings-shell"; export default function SettingsWorkspacePage() { const params = useParams<{ workspace: string }>(); const slugOrId = params?.workspace ?? ""; const { data: ws, isLoading } = useWorkspace(slugOrId); const qc = useQueryClient(); const [name, setName] = useState(null); const [emoji, setEmoji] = useState(null); const update = useUpdateWorkspace(ws?.id ?? ""); const displayName = name ?? ws?.name ?? ""; const displayEmoji = emoji ?? ws?.emoji ?? ""; // Role is compared case-insensitively: the API returns the raw UPPERCASE // Prisma role enum while the core WorkspaceRole type is lowercase // (flagged to the core agent). const role = (ws?.role ?? "").toLowerCase(); const isOwner = role === "owner"; const isAdminOrOwner = role === "owner" || role === "admin"; return ( {isLoading || !ws ? (

Loading…

) : (
{/* general */}
setEmoji(e.target.value)} />
setName(e.target.value)} />
{/* members shortcut */}

{ws.memberCount} member{ws.memberCount === 1 ? "" : "s"} ·{" "} Manage members & invitations

{/* danger zone */} {isAdminOrOwner && (
)}
)}
); } function TransferOwnership({ disabled, workspaceId }: { disabled: boolean; workspaceId: string }) { const members = useWorkspaceMembers(workspaceId); const qc = useQueryClient(); const [target, setTarget] = useState(""); const [busy, setBusy] = useState(false); const options = (members.data ?? []).filter((m: WorkspaceMember) => m.userId !== target); const transfer = async () => { if (!target) return; setBusy(true); try { await apiFetch(`/workspaces/${workspaceId}/transfer`, { method: "POST", body: { newOwnerId: target } }); toast("success", "Ownership transferred"); qc.invalidateQueries({ queryKey: ["workspaces", workspaceId] }); } catch (e) { toast("error", "Transfer failed", (e as Error).message); } finally { setBusy(false); } }; return (
); } function DeleteWorkspace({ disabled, workspaceId, workspaceName }: { disabled: boolean; workspaceId: string; workspaceName: string }) { const qc = useQueryClient(); const [confirmText, setConfirmText] = useState(""); const [busy, setBusy] = useState(false); const del = async () => { setBusy(true); try { await apiFetch(`/workspaces/${workspaceId}`, { method: "DELETE" }); toast("success", "Workspace deleted"); qc.removeQueries({ queryKey: ["workspaces", workspaceId] }); if (typeof window !== "undefined") window.location.href = "/"; } catch (e) { toast("error", "Delete failed", (e as Error).message); setBusy(false); } }; return (
setConfirmText(e.target.value)} placeholder={workspaceName} disabled={disabled} />
); }